package cn.skyquant.quant4j.jforex.strategy.yasha;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.forex.ForexConfigSeriesDTO;
import cn.skyquant.quant4j.api.forex.ForexServiceProxy;
import cn.skyquant.quant4j.api.quant.Bar;
import cn.skyquant.quant4j.api.quant.TimeBar;
import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.util.QuantUtil;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * 夜叉
 * 动态点位逆市加仓策略
 *
 * @author harley-dog
 */
@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class YashaBK extends BaseStrategy implements IBarFeedListener {
    @Configurable("基础净值（★重要★）")
    public int basic_equity = 20000;
    @Configurable("逆市是否开首仓")
    public boolean R_Trade = false;
    @Configurable("加仓数列列表")
    public String seriesIds = "102";
    @Configurable("寻找加仓数列的ATR倍率")
    public double m_atr = 2;
    @Configurable("寻找加仓数列的布林差倍率")
    public double m_boll = 1;
    @Configurable("大K柱时间间隔")
    public Period period_big = Period.DAILY;
    @Configurable("布林轨:计算K柱个数(也用作ATR个数)")
    public int big_nums = 20;
    @Configurable("布林轨:价格选择")
    public IIndicators.AppliedPrice appliedPrice = IIndicators.AppliedPrice.CLOSE;
    @Configurable("布林轨:上下边宽度")
    public double boll_width = 2;
    @Configurable("布林轨:均线类型")
    public IIndicators.MaType boll_ma_type = IIndicators.MaType.WMA;
    @Configurable("入场条件1:当天突破布林轨多少倍日波动率")
    public double first_atr = 0;
    @Configurable("入场条件2:之前穿越过布林轨且前n根K柱与布林轨顺势")
    public int k_num = 3;
    @Configurable("入场条件2:前n根K柱组合必须大于x倍ATR")
    public double x_atr = 1.5;
    @Configurable("出场条件1：止损x倍大K柱ATR")
    public double stop_loss_atr = 0.5;
    @Configurable("出场条件2：当天最高价大于布林带y倍波动率不开单且直接平仓")
    public double _boll_y = 0.5;
    @Configurable("开单允许最大滑点")
    public double slippage = 5;
    @Configurable("复利:开启复利")
    public boolean compound = true;
    @Configurable("1:货币,方向,倍率")
    public String str0 = "EURUSD,A,1";
    @Configurable("2:货币,方向,倍率")
    public String str1 = "GBPUSD,B,1";
    @Configurable("3:货币,方向,倍率")
    public String str2 = "USDCAD,S,1";
    @Configurable("4:货币,方向,倍率")
    public String str3 = "";
    @Configurable("5:货币,方向,倍率")
    public String str4 = "";
    @Configurable("6:货币,方向,倍率")
    public String str5 = "";
    @Configurable("7:货币,方向,倍率")
    public String str6 = "";
    @Configurable("8:货币,方向,倍率")
    public String str7 = "";
    @Configurable("9:货币,方向,倍率")
    public String str8 = "";
    @Configurable("10:货币,方向,倍率")
    public String str9 = "";
    @Configurable("订单成交日志")
    public boolean log_order_fill = true;
    @Configurable("订单平仓日志")
    public boolean log_order_close = true;
    @Configurable("订单提交日志")
    public boolean log_order_submit = true;
    @Configurable("订单合并日志")
    public boolean log_order_merge = false;
    @Configurable("订单取消日志")
    public boolean log_order_cancel = false;
    @Configurable("压力支撑位日志")
    public boolean log_ylzc = false;
    @Configurable("ATR,布林带日志")
    public boolean log_daily_open = false;
    @Configurable("不开单原因日志")
    public boolean log_order_reason = false;
    @Configurable("测试时是否显示图表")
    public boolean show_chart = false;

    ForexServiceProxy forexServiceProxy;
    ATRSeriesManager asm;

    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体
    final Map<Instrument, Double> atrMap = new HashMap<>();//大K柱ATR
    final Map<Instrument, Boll> boll_map = new HashMap<>();//记录昨天的布林轨
    final Map<Instrument, TradeDirection> barTrendMap = new HashMap<>();//记录K柱的趋势
    final Map<Instrument, ExTrend> bollTrendMap = new HashMap<>();//记录上次穿越布林轨的趋势
    double tp_sum = 0;
    double sl_sum = 0;

    @Override
    public final void onStopEx() {
        super.onStopEx();
        out(" 1.profit=%.2f", profit1);
        out(" 2.profit=%.2f", profit2);
        out(" 1&2.profit=%.2f", profit3);
        forexServiceProxy = null;
        asm = null;
        insMap.clear();
        atrMap.clear();
        barTrendMap.clear();
        bollTrendMap.clear();
        boll_map.clear();
    }

    @Override
    protected String getVersion() {
        return "018";
    }

    @Override
    protected String getName() {
        return "Yasha" + eaId;
    }

    @Override
    protected String getEaName() {
        return "Yasha";
    }

    @Override
    public void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    public void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    //初始化方法
    @Override
    public void init(InitEntity initEntity) {
        forexServiceProxy = new ForexServiceProxy(account.getAccountId(), BorkerType.DUKASCOPY);
        String[] strArr = {str0, str1, str2, str3, str4, str5, str6, str7, str8, str9};
        for (int i = 0; i < strArr.length; i++) {
            if (strArr[i] != null && strArr[i].trim().length() > 0) {
                String[] s = strArr[i].split(",");
                if (s.length == 3
                        && s[0].trim().length() > 0
                        && s[1].trim().length() > 0
                        && s[2].trim().length() > 0) {
                    Instrument instrument = Instrument.valueOf(s[0]);
                    if (instrument == null) {
                        if (log_order_reason) warn("%s is null", Instrument.valueOf(s[0]));
                        continue;
                    }
                    if (!insMap.containsKey(instrument)) {
                        insMap.put(instrument, new InstrumentEntity(instrument));
                    }
                    try {
                        String tradeDirection = s[1];
                        Double m = Double.parseDouble(s[2]);
                        //逆市
                        YashConfig a = new YashConfig(getName(), i, m, 0, instrument, tradeDirection);//逆趋势
                        initEntity.a.add(a);
                        //货币组
                        initEntity.b.add(instrument);
                        if (log_start_stop) {
                            out(a.toString());
                        }
                        IChart chart = context.getChart(instrument);
                        if (chart != null) {
                            IFeedDescriptor feedDescriptor = chart.getFeedDescriptor();
                            if (feedDescriptor != null) {
                                feedDescriptor.setPeriod(period_big);
                                feedDescriptor.setOfferSide(OfferSide.ASK);
                                chart.setFeedDescriptor(feedDescriptor);
                                chart.add(indicators.getIndicator("BBANDS"), new Object[]{big_nums, boll_width, boll_width, boll_ma_type.ordinal()});
                                if (show_chart) {
                                    chart.add(indicators.getIndicator("ATR"), new Object[]{big_nums});
                                    IIndicator tl_boll = indicators.getIndicator("TL_Boll");
                                    if (tl_boll != null) {
                                        chart.add(tl_boll, new Object[]{big_nums, boll_width, boll_width, boll_ma_type.ordinal()});
                                    }
                                }
                                chart.repaint();
                            }
                        }

                    } catch (NumberFormatException e) {
                        initEntity.addReason("init " + i + "config error:" + e);
                    }
                } else {
                    if (log_start_stop)
                        warn("config[%d] must be 3 params[symbol,direction,rate,k_atr,d_atr],but it find %d params", i, s.length);
                }
            }
        }
        if (initEntity.a.isEmpty()) {
            initEntity.addReason("Asura config is empty");
        }

        if (seriesIds == null || seriesIds.length() == 0) {
            initEntity.addReason("seriesIds is null");
            return;
        }

        String[] seriesIdArr = seriesIds.split(",");
        if (seriesIdArr.length == 0) {
            initEntity.addReason("seriesIds is invalid");
            return;
        }

        int[] seriesIdIntArr = new int[seriesIdArr.length];
        for (int i = 0; i < seriesIdArr.length; i++) {
            seriesIdIntArr[i] = Integer.parseInt(seriesIdArr[i]);
        }
        asm = new ATRSeriesManager(seriesIdIntArr);
        for (Series series : asm.seriesList) {
            out("%d.id total.pip=%d", series.seriesId, series.totalPip);
        }

        IBarFeedListener listener = this;
        initEntity.b.forEach(x -> {
            context.subscribeToBarsFeed(x, period, OfferSide.ASK, listener);//逆市运行间隔
            context.subscribeToBarsFeed(x, period_big, OfferSide.ASK, listener);//大周期间隔，计算布林轨和K柱用
        });
        initEntity.r = true;
        //启动日志
        if (super.log_start_stop) {
            try {
                //遍历所有订单
                List<IOrder> orderList = engine.getOrders();
                for (BaseConfig baseConfig : initEntity.a) {
                    if (!baseConfig.isValid()) {
                        continue;
                    }
                    if (baseConfig instanceof YashConfig) {
                        YashConfig c = (YashConfig) baseConfig;
                        OrderExManager o = getOrderExManager(c, orderList, YashOrderComment.class);
                        if (o.buy_last != null) {
                            out("%s-[%d], %s, LONG  order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.buy_last.seriesIndex,
                                    o.buy_last.order.getAmount(),
                                    ((YashOrderComment) o.buy_last.orderComment).pre_open_price,
                                    o.buy_last.order.getTakeProfitPrice(),
                                    o.buy_last.order.getStopLossPrice(),
                                    o.buyLimitList.isEmpty() ? 0 : o.buyLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, LONG  has no order", c.name, c.index, c.instrument.name());
                        }
                        if (o.sell_last != null) {
                            out("%s-[%d], %s, SHORT order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.sell_last.seriesIndex,
                                    o.sell_last.order.getAmount(),
                                    ((YashOrderComment) o.sell_last.orderComment).pre_open_price,
                                    o.sell_last.order.getTakeProfitPrice(),
                                    o.sell_last.order.getStopLossPrice(),
                                    o.sellLimitList.isEmpty() ? 0 : o.sellLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, SHORT  has no order", c.name, c.index, c.instrument.name());
                        }
                    }
                }
            } catch (JFException e) {
                err("init error:", e.getMessage());
            }
        }

    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        if (period.equals(period_big)) {
            dailyOpen(instrument, bar.getTime());
        }
        try {
            ITimeDomain timeDomain0;
            ITimeDomain timeDomain1;
            try {
                timeDomain0 = dataService.getOfflineTimeDomain(0);
                timeDomain1 = dataService.getOfflineTimeDomain(-1);
            } catch (JFException e) {
                if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
                return;
            }
            if (timeDomain0 == null || timeDomain1 == null) {
                if (log_error) warn("onBar.getOffline,TimeDomain is null");
                return;
            }
            long time = context.getTime();


            //停盘时间不干活
            if (timeDomain1.getEnd() + period.getInterval() > time || time > timeDomain0.getStart() - period.getInterval()) {
                return;
            }

            if (period.equals(this.period)) {
                configMap.entrySet().stream()
                        .filter(x -> x.getValue().isValid()
                                && x.getValue() instanceof YashConfig
                                && getName().equals(((YashConfig) x.getValue()).name)
                                && ((YashConfig) x.getValue()).instrument == instrument)
                        .forEach(x -> Rprocess((YashConfig) x.getValue(), bar.getTime()));
            }
        } catch (Throwable e) {
            for (StackTraceElement se : e.getStackTrace()) {
                err("error %s", se.toString());
            }
        }
    }

    //开盘时要干的事儿
    private void dailyOpen(Instrument instrument, long time) {
        calcATR(instrument, time);//计算波动率
        calcBoll(instrument, time);//计算布林带
        calcBar(instrument, time);//计算布林带
    }

    //计算ATR
    private void calcATR(Instrument instrument, long time) {
        atrMap.remove(instrument);
        InstrumentEntity ie = insMap.get(instrument);
        double[] atrs = null;
        try {
            atrs = indicators.atr(instrument, period_big, OfferSide.BID, big_nums, Filter.WEEKENDS, 1, time, 0);
        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
        }
        if (atrs != null && atrs.length > 0 && atrs[0] != 0) {
            atrMap.put(instrument, atrs[0]);
            if (log_daily_open) out("%s atr[%d]=%s", instrument, big_nums, ie.formatPrice(atrs[0]));
        }
    }


    //计算布林带
    private void calcBoll(Instrument instrument, long time) {
        InstrumentEntity ie = insMap.get(instrument);
        bollTrendMap.remove(instrument);
        try {
            List<IBar> askBarList = history.getBars(instrument, period_big, OfferSide.ASK, Filter.WEEKENDS, big_nums, time, 0);
            List<IBar> bidBarList = history.getBars(instrument, period_big, OfferSide.BID, Filter.WEEKENDS, big_nums, time, 0);
            if (askBarList == null || askBarList.size() < 2 || bidBarList == null || bidBarList.size() < 2 || askBarList.size() != bidBarList.size()) {
                return;
            }
            List<TimeBar> askTimeBarList = QuantUtil.batchConvertIBarToTimeBar(askBarList);
            List<TimeBar> bidTimeBarList = QuantUtil.batchConvertIBarToTimeBar(bidBarList);

            double[][] boll_asks = indicators.bbands(instrument, period_big, OfferSide.ASK, appliedPrice, big_nums, boll_width, boll_width, boll_ma_type, Filter.WEEKENDS, big_nums, time, 0);
            double[][] boll_bids = indicators.bbands(instrument, period_big, OfferSide.BID, appliedPrice, big_nums, boll_width, boll_width, boll_ma_type, Filter.WEEKENDS, big_nums, time, 0);

            if (boll_asks == null || boll_asks.length != 3 || boll_asks[0].length != big_nums || boll_bids == null || boll_bids.length != 3 || boll_bids[0].length != big_nums) {
                return;
            }

            //bar柱和boll都是从当天开始算起，然而当天的bar柱和boll都不能要，要取昨天的
            Boll boll_last = new Boll(
                    new BigDecimal(boll_asks[0][big_nums - 1]).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                    new BigDecimal(boll_asks[1][big_nums - 1]).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                    new BigDecimal(boll_bids[2][big_nums - 1]).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                    new BigDecimal(boll_asks[0][big_nums - 1] - boll_bids[2][big_nums - 1]).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue());
            if (log_daily_open) out("boll_last=%s", boll_last);
            boll_map.put(instrument, boll_last);
            for (int i = askTimeBarList.size() - 1; i >= 0; i--) {
                TimeBar askBar = askTimeBarList.get(i);
                TimeBar bidBar = bidTimeBarList.get(i);
                double high = askBar.high;
                double low = bidBar.low;
                double boll_up = boll_asks[0][i];
                double boll_mid = boll_asks[1][i];
                double boll_low = boll_bids[2][i];
                double boll_diff = boll_up - boll_low;
                Boll boll = new Boll(
                        new BigDecimal(boll_up).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                        new BigDecimal(boll_mid).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                        new BigDecimal(boll_low).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue(),
                        new BigDecimal(boll_diff).setScale(ie.scale + 1, RoundingMode.HALF_UP).doubleValue());
                if (high > boll_up && low < boll_low) {
                    break;
                }
                if (high > boll_up) {
                    ExTrend exTrend = new ExTrend(boll, askBar, TradeDirection.LONG);
                    if (log_daily_open) out("last boll trend=%s", exTrend);
                    bollTrendMap.put(instrument, exTrend);
                    break;
                }
                if (low < boll_low) {
                    ExTrend exTrend = new ExTrend(boll, bidBar, TradeDirection.SHORT);
                    bollTrendMap.put(instrument, exTrend);
                    if (log_daily_open) out("last boll trend=%s", exTrend);
                    break;
                }
            }

        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
        }
    }

    //计算bar
    private void calcBar(Instrument instrument, long time) {
        barTrendMap.remove(instrument);
        if (!atrMap.containsKey(instrument)) {
            return;
        }
        double atr = atrMap.get(instrument);
        try {
            if (k_num > 0) {
                List<IBar> barList = history.getBars(instrument, period_big, OfferSide.BID, Filter.WEEKENDS, k_num, time, 0);
                List<TimeBar> timeBarList = QuantUtil.batchConvertIBarToTimeBar(barList);
                Bar bar = TimeBar.merge(timeBarList);
                out("bar.open=%.5f,bar.close=%.5f,bar_size=%.5f,atr*x_atr=%.5f", bar.open, bar.close, Math.abs(bar.close - bar.open), atr * x_atr);
                if (bar.open < bar.close && (bar.close - bar.open) > atr * x_atr) {
                    barTrendMap.put(instrument, TradeDirection.LONG);
                } else if (bar.open > bar.close && (bar.open - bar.close) > atr * x_atr) {
                    barTrendMap.put(instrument, TradeDirection.SHORT);
                }
                if (log_daily_open && barTrendMap.get(instrument) != null)
                    out("%s.trend = %s", instrument.name(), barTrendMap.get(instrument).name());
            }
        } catch (JFException e) {
            err("calcBar error:%s", e.getMessage());
        }
    }


    //开始运行
    private void Rprocess(YashConfig x, long time) {
        //先做第一遍过滤，过滤太长的时间，过滤点差,过滤找不到压力支撑位，ATR，布林值等等因素,这些事理论上来说都是不存在的
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        if (ie == null) {
            return;
        }
        if (!atrMap.containsKey(instrument) || atrMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find atr with %s", instrument.name(), atrMap.toString());
        }
        ITick tick;
        try {
            tick = history.getLastTick(instrument);
        } catch (JFException e) {
            if (log_error) warn("%s get Last Tick error:%s", instrument.name(), e);
            return;
        }
        long time_diff = time - tick.getTime();
        if ((time - tick.getTime()) > 2 * period.getInterval()) {
            if (log_order_reason)
                out("%s,tick time=%s,time.diff=%d s > %d s,can't open order", instrument.name(), CalendarUtils.formatStandard(tick.getTime()), time_diff / 1000, (2 * period.getInterval() / 1000));
            return;
        }

        //找出所有的订单
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            if (log_order_reason) warn("%s getOrders error:%s", instrument, e.getMessage());
            return;
        }
        OrderExManager orderExManager = getOrderExManager(x, orderList, YashOrderComment.class);
        if (x.tradeDirection == TradeDirection.LONG || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.LONG, orderExManager);
        }
        if (x.tradeDirection == TradeDirection.SHORT || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.SHORT, orderExManager);
        }
    }

    private void RprocessByType(YashConfig x, InstrumentEntity ie, ITick tick, TradeDirection tradeDirection, OrderExManager orderExManager) {
        List<OrderEx> orderExList = orderExManager.getOrderExList(tradeDirection);
        List<OrderEx> orderLimitExList = orderExManager.getLimitOrderExList(tradeDirection);
        OrderEx last = orderExManager.getLast(tradeDirection);
        OrderEx first = orderExManager.getFirst(tradeDirection);

        //如果当前有两仓，就不对了，表示没有合并仓位，需要合并仓位
        if (orderExList.size() > 1) {
            merge(x.instrument, ie, x.name, x.index, tradeDirection);
            return;
        }

        Boll boll = boll_map.get(x.instrument);
        if (boll == null || atrMap.get(x.instrument) == null) {
            return;
        }
        double atr = atrMap.get(x.instrument); //atr 这里拿到的是日ATR

        try {
            IBar bar0 = history.getBar(x.instrument, period_big, tradeDirection == TradeDirection.LONG ? OfferSide.ASK : OfferSide.BID, 0);
            if (
                    (tradeDirection == TradeDirection.LONG && bar0.getHigh() > boll.up + atr * _boll_y) ||
                            (tradeDirection == TradeDirection.SHORT && bar0.getLow() < boll.low - atr * _boll_y)) {
                if (last != null) {
                    closeLimit(last);//取消挂单
                    List<IOrder> orderList = new ArrayList<>();
                    orderList.add(last.order);
                    closeOrders(orderList);//平掉现有的单子
                    if (log_order_close) warn("%s %s angerous!!  %s %s %s,close All", x.instrument.name(),
                            tradeDirection.name(),
                            tradeDirection == TradeDirection.LONG ? bar0.getHigh() : bar0.getLow(),
                            tradeDirection == TradeDirection.LONG ? ">" : "<",
                            tradeDirection == TradeDirection.LONG ? boll.up : boll.low,
                            tradeDirection == TradeDirection.LONG ? "+" : "0",
                            ie.formatPrice(atr * _boll_y));
                }
                return;
            }
        } catch (JFException e) {
            return;
        }

        //如果有限价挂单,则不做任何处理
        if (!orderLimitExList.isEmpty()) {
            return;
        }


        //如果空仓则开首仓
        if (orderExList.isEmpty() && orderLimitExList.isEmpty() && R_Trade) {
            //计算暂停时间是否达标
            openFirstOrder(x, tick, tradeDirection);
            return;
        }

        //如果上一仓不为null，并且上一仓是当前序列里唯一的一仓，并且上一仓属于普通加仓
        if (last != null && first != null && last.equals(first) && last.orderComment.type == OpenType.P && last.orderComment instanceof YashOrderComment) {
            //如果不是空仓，又没有限价单，则直接开下一仓
            addOrder(x, ie, tradeDirection, tick, last);//加仓
        }
    }

    //开首仓
    private void openFirstOrder(
            YashConfig x,     //配置
            ITick tick,         //最后一次报价
            TradeDirection tradeDirection    //交易方向
    ) {
        //step.1 获取ATR
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        if (ie == null) {
            return;
        }
        double atr = atrMap.get(instrument); //atr 这里拿到的是日ATR
        Boll boll = boll_map.get(instrument);//
        if (boll == null) {
            return;
        }
        double series_price = atr * m_atr + boll.diff * m_boll;
        int atr_series_pip = (int) (series_price * ie.pipMulti);
        Series series = asm.getSeriesByValue(atr_series_pip);
        if (series == null) {
            return;
        }
        //首先要确定atr
        ForexConfigSeriesDTO sm0 = series.get(0);
        if (sm0 == null) {
            return;
        }
        //step.3 判断能否开首仓
        //首先判断不能开仓条件，当天布林轨上穿了x倍波动率，并且被打下来了

        // 越过布林轨上下边缘多少倍波动率才可以开仓
        double currentPrice = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();
        int in_method = 0;//默认不能入场
        double boll_value = 0;//记录布林值
        boolean can_in_1 = false;
        boolean can_in_2 = false;
        //入场条件1判断
        ExTrend exTrend = bollTrendMap.get(instrument);
        TradeDirection barTrend = barTrendMap.get(instrument);

        if (tradeDirection == TradeDirection.LONG && currentPrice > boll.up + first_atr * atr) {
            //判断一个否的条件，曾经上穿过布林轨，并且被打下来了
            boll_value = boll.up;
            can_in_1 = true;
            in_method = 1;
            /*
            if(exTrend!=null){
                Boll boll_ex = exTrend.boll;
                TimeBar timeBar = exTrend.bar;
                if(timeBar.high - boll_ex.up > atr*stop_loss_atr && timeBar.close <= boll_ex.up){
                    can_in_1 = false;
                    in_method = 0;
                }
            }*/
        }

        if (tradeDirection == TradeDirection.SHORT && currentPrice < boll.low - first_atr * atr) {
            boll_value = boll.low;
            can_in_1 = true;
            in_method = 1;
            /*
            if(exTrend!=null){
                Boll boll_ex = exTrend.boll;
                TimeBar timeBar = exTrend.bar;
                if(boll_ex.low - timeBar.low > atr*stop_loss_atr && timeBar.close >= boll_ex.low){
                    can_in_1 = false;
                    in_method = 0;
                }
            }*/
        }

        //入场条件2判断
        if (k_num > 0 && exTrend != null && exTrend.td == tradeDirection && barTrend == tradeDirection && (context.getTime()-exTrend.bar.time) >= period_big.getInterval()*k_num) {
            can_in_2 = true;
            boll_value = exTrend.td == TradeDirection.LONG ? exTrend.boll.up : exTrend.boll.low;
            in_method = 2;
        }
        if (can_in_1 && can_in_2) {
            in_method = 3;
        }
        if (in_method == 0) {
            return;
        }

        if (can_in_1==false && can_in_2==false) {
            return;
        }

        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUY : IEngine.OrderCommand.SELL;

        //step.4 开始设置止损价和止盈价
        double sl = tradeDirection == TradeDirection.LONG ? new BigDecimal(boll.low - stop_loss_atr * atr).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(boll.up + stop_loss_atr * atr).setScale(ie.scale, RoundingMode.DOWN).doubleValue();

        //计算止盈
        double tp_price = sm0.backpip * ie.pipValue;//回调点位
        double tp = tradeDirection == TradeDirection.LONG ? new BigDecimal(currentPrice + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(currentPrice - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();

        double compou = compound ? account.getEquity() / basic_equity : 1;
        double amount = Math.max(ie.minAmount, ie.minAmount * x.m * compou * sm0.prs);//首仓直接就是用最小仓位*倍率*复利倍率*首仓倍率
        int first_equit = (int) account.getEquity();
        YashOrderComment orderComment = new YashOrderComment(series.seriesId, 0, OpenType.P, first_equit, context.getTime(), 0, in_method, boll_value, currentPrice, currentPrice, currentPrice, 1);
        OrderEx orderEx = new OrderEx(x.name, LabelType.M, x.index, 0, instrument, tradeDirection);
        try {
            if (engine.getOrder(orderEx.toString()) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        String comment = orderComment.toString();
        try {
            engine.submitOrder(orderEx.toString(), x.instrument, orderCommand, amount, 0, slippage, sl, tp, 0, comment);
        } catch (JFException e) {
            warn("config[%d] %s open first order error:%s", x.index, instrument, e.getMessage());
        }
    }

    /**
     * 加仓
     *
     * @param x    Asura配置
     * @param ie   交易品种属性
     * @param tick 最后一次报价
     * @param last 当前尾仓
     */
    private void addOrder(
            YashConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            ITick tick,         //最后一次报价
            OrderEx<YashOrderComment> last       //尾仓
    ) {
        Instrument instrument = x.instrument;
        int index = last.seriesIndex + 1;//下一仓
        //step.1 获取ATR和布林带
        double atr = atrMap.get(instrument); //atr 这里拿到的是日ATR
        Boll boll = boll_map.get(instrument);
        if (boll == null) {
            return;
        }
        double series_value = atr * m_atr + boll.diff * m_boll;
        int atr_series_pip = (int) (series_value * ie.pipMulti);
        Series series = asm.getSeriesByValue(atr_series_pip);
        //开到末仓不能加仓
        if (series == null || index >= series.list.size()) {
            return;
        }
        ForexConfigSeriesDTO sm = series.list.get(index);//本次加仓数列

        //step.3 计算开仓时的价格
        double current_price = tradeDirection == TradeDirection.LONG ? tick.getAsk() : tick.getBid();                 //当前价格
        IEngine.OrderCommand orderCommand = tradeDirection == TradeDirection.LONG ? IEngine.OrderCommand.BUYLIMIT : IEngine.OrderCommand.SELLLIMIT;

        double last_price;
        if (last.orderComment.series_index == 0) {
            last_price = last.order.getOpenPrice();//最后一仓开仓的价格
        } else {
            last_price = last.orderComment.open_price;//最后一仓开仓的价格
        }
        double add_price = sm.addpip * ie.pipValue;//当前仓位要求的加仓点距
        double exp_open_price = tradeDirection == TradeDirection.LONG ? last_price - add_price : last_price + add_price;//预期的开仓价格

        double open_price = tradeDirection == TradeDirection.LONG ? new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(exp_open_price).setScale(ie.scale, RoundingMode.UP).doubleValue();
        //真实开仓价格必须在首仓找到的压力支撑位以上
        if (tradeDirection == TradeDirection.LONG && open_price < boll.low) {
            if (log_order_reason && log_ylzc)
                out("config[%d] %s open %d LONG order, last_price=%s, add_price=%s,open_price=%s < boll.low:%s ,can't add order",
                        x.index, instrument.name(), index, last_price, ie.formatPrice(add_price), open_price, boll.low);
            return;
        }
        if (tradeDirection == TradeDirection.SHORT && open_price > boll.up) {
            if (log_order_reason && log_ylzc)
                out("config[%d] %s open %d SHORT order, last_price=%s, add_price=%d,open_price=%s > boll.up:%s ,can't add order",
                        x.index, instrument.name(), index, last_price, ie.formatPrice(add_price), open_price, boll.up);
            return;
        }

        double last_order_amount = last.order.getAmount();//上一仓数量
        double last_avg_price = last.order.getOpenPrice();//上一仓的平均价格
        double loss_money = ie.getMoney(tradeDirection == TradeDirection.LONG ? open_price - last_avg_price : last_avg_price - open_price, last_order_amount);//到达开仓点位将会损失的钱
        double loss_charge = ie.getCharge(last_order_amount);
        double real_total_loss = loss_money - loss_charge;//真实亏损（加上手续费）
        double compou = compound ? account.getEquity() / basic_equity : 1;
        double amount_open = Math.max(ie.minAmount, ie.minAmount * x.m * compou * sm.prs);

        //step.6 开仓
        OrderEx orderEx = new OrderEx(x.name, LabelType.N, x.index, index, x.instrument, tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                if (log_order_reason) {
                    out("order.label[%s] exist ,can't open order", label);
                }
                return;
            }
        } catch (JFException e) {
            err("getOrder error:%s", e.getMessage());
        }
        YashOrderComment orderComment = new YashOrderComment(series.seriesId, index, OpenType.P,
                last.orderComment.first_equit,
                last.orderComment.first_time.getTime(), (int) real_total_loss,
                last.orderComment.in_method,
                last.orderComment.boll, last.orderComment.first_open_price, last.orderComment.open_price, open_price, 1);
        String comment = orderComment.toString();
        long goodTillTime = context.getTime() + period_big.getInterval() - Period.TEN_SECS.getInterval();
        try {
            engine.submitOrder(label, x.instrument, orderCommand, amount_open, open_price, slippage, 0, 0, goodTillTime, comment);
        } catch (JFException e) {
            if (log_error)
                warn("[label=%s,current_price=%s,open_price=%s,amount_open=%s],error=%s", label, current_price, open_price, ie.formatAmout(amount_open), e.getMessage());
        }
    }

    /**
     * 整个流程
     * 1. M首仓(带sl,tp) -> 2.N加仓(sl0,tp0) -> 3.修改M仓(sl0,tp0) -> 4.合并同类订单(计算压力支撑位),生成新M - >5.修改新M仓止盈止损价
     */
    @Override
    public void onMessageEx(IMessage message) {
        try {
            IOrder order = message.getOrder();
            if (order == null) {
                return;
            }
            switch (message.getType()) {
                case ORDER_FILL_REJECTED:
                case ORDER_SUBMIT_REJECTED:
                case ORDERS_MERGE_REJECTED:
                case ORDER_CHANGED_REJECTED:
                    if (log_order_reason && log_error) err(message.toString());
                    break;
            }
            String label = order.getLabel();
            if (label == null) {
                return;
            }
            String[] label_arr = label.split("_");
            if (label_arr.length < 1) {
                return;
            }
            if ((getName()).equalsIgnoreCase(label_arr[0])) {
                OrderEx<YashOrderComment> orderEx = new OrderEx(order, new YashOrderComment());
                //监听逆势仓的单子
                if (!orderEx.valid(getName())) {
                    return;
                }
                ROnMessage(message, orderEx, order);
            }
        } catch (Throwable e) {
            err("on message,error,%s", e.getMessage());
        }
    }

    double profit1 = 0;
    double profit2 = 0;
    double profit3 = 0;

    private void ROnMessage(IMessage message, OrderEx<YashOrderComment> orderEx, IOrder order) {
        //获取订单的基本参数
        InstrumentEntity ie = new InstrumentEntity(order.getInstrument());
        IAccount account = this.context.getAccount();
        YashOrderComment orderComment = orderEx.orderComment;
        int first_equit = orderComment.first_equit;
        int current_equity = (int) account.getEquity();
        int profit = (int) (account.getEquity() - orderComment.first_equit);
        int index = orderComment.series_index;
        int seriesId = orderComment.series_id;
        LabelType labelType = orderEx.labelType;
        double openPrice = order.getOpenPrice();
        double closePrice = order.getClosePrice();
        double amount = order.getAmount();
        Instrument instrument = order.getInstrument();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        String instrumentName = order.getInstrument().name();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        int dist = Math.abs(ie.priceToPip(orderEx.orderComment.first_open_price - orderEx.orderComment.open_price));
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if (log_order_fill) {
                    if (sl != 0 && tp != 0) {
                        notif("%s %s, [%d-%5d]=%5d,Fill[%d] %d-%d Order:%s.%s,amount=%s,sl=%s,tp=%s,order_comment=[%s]", getName(), getVersion(),
                                first_equit, current_equity, profit, orderComment.in_method, seriesId, index, instrumentName, orderCommand, amount, sl, tp, order.getComment());
                    } else {
                        notif("%s %s, [%d-%5d]=%5d,Fill[%d] %d-%d Order:%s.%s,amount=%s,open=%s,order_comment=[%s]", getName(), getVersion(),
                                first_equit, current_equity, profit, orderComment.in_method, seriesId, index, instrumentName, orderCommand, amount, openPrice, orderComment.next_add_r, order.getComment());
                    }
                }
                //如果是加仓订单成交，则修改止盈止损值
                if (labelType == LabelType.N) {
                    cancelSLAndTP(orderEx);
                }
                break;
            case ORDER_CLOSE_OK:
                if (order.getState() == IOrder.State.CLOSED) {
                    closeLimit(orderEx);
                    if (log_order_reason && log_order_merge && message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_MERGE)) {
                        out("%s %s, [%d-%5d]=%5d,Close by merge %d-%d Order[%s.%s ,amount=%s,open=%s]", getName(), getVersion(),
                                first_equit, current_equity, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice);
                    }
                    if (order.getProfitLossInUSD() > 0) {
                        sl_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            info("%s %s, [%d-%5d]=%5d,Close by TP %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],profit=%s,total_tp=%.2f", getName(), getVersion(),
                                    first_equit, current_equity, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, sl, tp, closePrice, order.getProfitLossInUSD(), sl_sum);
                        }
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                    }
                    if (order.getProfitLossInUSD() < 0) {
                        tp_sum += order.getProfitLossInUSD();
                        if (log_order_close) {
                            err("%s %s, [%d-%5d]=%5d, Close by SL %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],profit=%s,total_sl=%.2f", getName(), getVersion(),
                                    first_equit, current_equity, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, sl, tp, closePrice, order.getProfitLossInUSD(), tp_sum);
                        }
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                    }
                    if (order.getProfitLossInUSD() != 0) {
                        switch (orderComment.in_method) {
                            case 1:
                                profit1 += order.getProfitLossInUSD();
                                break;
                            case 2:
                                profit2 += order.getProfitLossInUSD();
                                break;
                            case 3:
                                profit3 += order.getProfitLossInUSD();
                                break;
                        }
                    }
                } else if (order.getState() == IOrder.State.CANCELED) {
                    if (log_order_cancel) {
                        out("%s %s, Cancel Order %d-%d %s [%s]", getName(), getVersion(), orderEx.orderComment.series_id, orderEx.seriesIndex, orderCommand.name(), order.getComment());
                    }
                }
                break;
            case ORDERS_MERGE_OK:
                Boll boll = boll_map.get(instrument);
                double atr = atrMap.get(order.getInstrument());
                double newSL = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(boll.low - atr * stop_loss_atr).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(boll.up + atr * stop_loss_atr).setScale(ie.scale, RoundingMode.UP).doubleValue();
                double open_price = orderEx.orderComment.open_price;//开仓价
                Series series = asm.getSeriesById(orderEx.orderComment.series_id);
                ForexConfigSeriesDTO sm = series.get(orderEx.seriesIndex);
                if (sm == null) {
                    break;
                }
                if (!atrMap.containsKey(instrument)) {
                    break;
                }

                double tp_price = sm.backpip * ie.pipValue;
                double newTP = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(open_price + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(open_price - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
                OfferSide offerSide = orderCommand == IEngine.OrderCommand.BUY ? OfferSide.ASK : OfferSide.BID;
                try {
                    order.setStopLossPrice(newSL, offerSide, 0);
                    order.setTakeProfitPrice(newTP);
                } catch (JFException e) {
                    err("order.setTakeProfitPrice error:%s", e.getMessage());
                }
                if (log_order_merge) {
                    out("%s %s, Merge Order[%s.%s,amount=%s,avg_open_price=%s,backup=%d,sl=%s,tp=%s,loss=%s],comment=%s",
                            getName(), getVersion(), instrumentName, orderCommand, amount, openPrice, sm.backpip, newSL, newTP, order.getProfitLossInUSD(), order.getComment());
                }
                break;
            case ORDER_CHANGED_OK:
                //修改止盈止损价成功,判断是不是M仓的止盈止损价都修改为0了
                if (orderEx.labelType == LabelType.M && sl == 0 && tp == 0) {
                    //进行合并仓位
                    merge(order.getInstrument(), ie, orderEx.name, orderEx.configIndex, orderEx.tradeDirection);
                }
                break;
            case ORDER_SUBMIT_OK:
                if (log_order_submit && isTestMode()) {
                }
                break;
            default:
                break;
        }

    }

    //2.修改M仓
    private void cancelSLAndTP(OrderEx orderExNew) {
        //获取同类的M仓
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(orderExNew.instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> sameMList = getSameOrders(orderList, orderExNew.instrument, orderExNew.name, orderExNew.configIndex, orderExNew.tradeDirection, LabelType.M, YashOrderComment.class);
        updateSLTP(sameMList, 0, 0);//将所有价格修改为0
    }

    private Lock mergeLock = new ReentrantLock();

    //3.合并N和M仓
    private void merge(Instrument instrument, InstrumentEntity ie, String name, int configIndex, TradeDirection tradeDirection) {
        if (!mergeLock.tryLock()) {
            return;
        }
        mergeLock.lock();
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> mergeOrderExList = getSameOrders(orderList, instrument, name, configIndex, tradeDirection, null, YashOrderComment.class);
        List<OrderEx> invalidOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() != 0 || x.order.getStopLossPrice() != 0)
                .collect(Collectors.toList());
        if (!invalidOrderList.isEmpty()) {
            updateSLTP(invalidOrderList, 0, 0);
            return;
        }
        List<IOrder> mergeOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() == 0 && x.order.getStopLossPrice() == 0)
                .map(x -> x.order)
                .collect(Collectors.toList());
        //如果需要合并的订单只有1个，则放弃merge
        if (mergeOrderExList.size() < 2) {
            return;
        }
        OrderEx<YashOrderComment> last_N = mergeOrderExList.get(mergeOrderExList.size() - 1); //N仓（最新的仓）
        OrderEx<YashOrderComment> orderEx = new OrderEx(last_N.name, LabelType.M, last_N.configIndex, last_N.seriesIndex, last_N.instrument, last_N.tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        int series_index = last_N.orderComment.series_index;
        int series_id = last_N.orderComment.series_id;
        out("series_id=%d", series_id);
        Series series = asm.getSeriesById(series_id);
        ForexConfigSeriesDTO sm_next = series.get(series_index + 1);

        //需要自己算的就是跟下一仓有关的
        int first_equit = last_N.orderComment.first_equit;
        Date first_time = last_N.orderComment.first_time;
        int this_profitloss = last_N.orderComment.this_profitloss;
        double boll = last_N.orderComment.boll;
        int in_method = last_N.orderComment.in_method;
        double first_open_price = last_N.orderComment.first_open_price;
        double pre_open_price = last_N.orderComment.pre_open_price;
        double current_open_price = last_N.order.getOpenPrice();
        OpenType openType = OpenType.P;
        if (sm_next == null) {
            //如果加到尾仓，则进行止损
            openType = OpenType.L;
        }
        //下面两个数值，
        YashOrderComment orderComment = new YashOrderComment(series_id, series_index, openType, first_equit, first_time.getTime(), this_profitloss, in_method, boll, first_open_price, pre_open_price, current_open_price, 1);
        String comment = orderComment.toString();
        try {
            IOrder order = engine.mergeOrders(label, comment, mergeOrderList);
            order.waitForUpdate(2000, IOrder.State.FILLED);
        } catch (JFException e) {
            warn("config[%d] %s.%s merge order[%s]-[%s]:%s", configIndex, instrument.name(), tradeDirection.name(), label, comment, e.getMessage());
        }
        mergeLock.unlock();
    }

    //删除同方向的挂单
    private void closeLimit(OrderEx closeOrderEx) {
        try {
            List<IOrder> orderList = engine.getOrders(closeOrderEx.instrument);
            for (IOrder order : orderList) {
                OrderEx orderEx = new OrderEx(order, new YashOrderComment());
                if (!orderEx.valid(closeOrderEx.name, closeOrderEx.configIndex)) {
                    continue;
                }
                TradeDirection tradeDirection = closeOrderEx.tradeDirection;
                IEngine.OrderCommand o2 = order.getOrderCommand();
                if ((tradeDirection == TradeDirection.LONG && o2 == IEngine.OrderCommand.BUYLIMIT)
                        || (tradeDirection == TradeDirection.SHORT && o2 == IEngine.OrderCommand.SELLLIMIT)) {
                    if (order.getState() == IOrder.State.OPENED) {
                        order.close();
                        order.waitForUpdate(500, IOrder.State.CANCELED);
                    }
                }
            }
        } catch (JFException e) {
            if (log_error)
                out("config[%d] close %s Limit error:", closeOrderEx.configIndex, closeOrderEx.tradeDirection.name());
        }
    }

    @Override
    public void onAccountEx(IAccount account) {
    }

    //加仓数列管理
    final class ATRSeriesManager {
        final List<Series> seriesList = new ArrayList();
        boolean valid;

        ATRSeriesManager(int[] seriesIds) {
            for (int seriesId : seriesIds) {
                addSeries(seriesId);
            }
            seriesList.sort((a, b) -> a.totalPip - b.totalPip);//按照totalPip从小到大排列
            valid = seriesList.size() == seriesIds.length;
        }

        void addSeries(int seriesId) {
            Series series = new Series(seriesId);
            seriesList.add(series);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (Series series : seriesList) {
                sb.append(series.seriesId)
                        .append(".series.num=")
                        .append(series.list.size())
                        .append(",totalPip=")
                        .append(series.totalPip).append("\n");
            }
            return sb.toString();
        }

        //根据波动率找到对应的加仓数列
        Series getSeriesByValue(int pip) {
            if (pip > seriesList.get(seriesList.size() - 1).totalPip) {
                return seriesList.get(seriesList.size() - 1);
            }
            if (pip < seriesList.get(0).totalPip) {
                return seriesList.get(0);
            }
            int max_diff = seriesList.get(seriesList.size() - 1).totalPip;
            Series result = null;
            for (Series series : seriesList) {
                int diff = Math.abs(series.totalPip - pip);
                if (diff < max_diff) {
                    max_diff = diff;
                    result = series;
                }
            }
            return result;
        }

        //根据波动率找到对应的加仓数列
        Series getSeriesById(int id) {
            for (Series series : seriesList) {
                if (series.seriesId == id) {
                    return series;
                }
            }
            return null;
        }
    }

    //加仓数列
    final class Series {
        final int seriesId;//加仓数列ID
        List<ForexConfigSeriesDTO> list;
        int totalPip;

        Series(int seriesId) {
            this.seriesId = seriesId;
            ResultDTO<List<ForexConfigSeriesDTO>> resultDTO = forexServiceProxy.findConfigSeries(seriesId);
            if (!resultDTO.okData() || resultDTO.data.isEmpty()) {
                return;
            }
            list = resultDTO.data.stream().filter(x -> x.backpip != null && x.backpip != 0 && x.addpip != null && x.addpip != 0 && x.prs != null && x.prs > 0).collect(Collectors.toList());
            for (ForexConfigSeriesDTO seriesDTO : list) {
                totalPip += seriesDTO.addpip;
            }
        }

        @Override
        public String toString() {
            return "ATRSeries,series.length" + list.size();
        }

        ForexConfigSeriesDTO get(int index) {
            return index < 0 || index > list.size() - 1 ? null : list.get(index);
        }

        int size() {
            return list.size();
        }
    }

    //阿波罗逆市单配置
    final static class YashConfig extends BaseConfig {
        final Instrument instrument;//货币
        final TradeDirection tradeDirection;//交易方向

        YashConfig(String name, int index, double m, double lossLimit, Instrument instrument, String tradeDirection) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
            this.tradeDirection = TradeDirection.parse(tradeDirection);
        }

        @Override
        public String toString() {
            return "Asura_" + index
                    + "_" + instrument.name()
                    + "_o_" + tradeDirection.name()
                    + "_m_" + m;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m >= 1 && tradeDirection != null;
        }
    }

    /**
     * 数列id_第几仓_加仓类型_首仓净值_首仓时间_首仓价格_下一次加仓倍率
     */
    final static class YashOrderComment extends BaseOrderComment {
        int in_method;              //入场类型
        double boll;                //布林轨价格
        double first_open_price;    //首仓price   7
        double pre_open_price;      //上一仓price 8
        double open_price;          //当前仓price 9
        double next_add_r;          //下一次加仓点位倍率 10

        YashOrderComment() {
            super();
        }

        public void parse(String comment) {
            super.parse(comment);
            if (s.length >= getParamNum()) {
                try {
                    in_method = Integer.parseInt(s[6]);
                    boll = Double.parseDouble(s[7]);
                    first_open_price = Double.parseDouble(s[8]);
                    pre_open_price = Double.parseDouble(s[9]);
                    open_price = Double.parseDouble(s[10]);
                    next_add_r = Double.parseDouble(s[11]);
                    valid = true;
                } catch (Exception e) {
                    valid = false;
                }
            }
        }

        YashOrderComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss, int in_method, double boll, double first_open_price, double pre_open_price, double open_price, double next_add_r) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
            this.in_method = in_method;
            this.boll = boll;
            this.first_open_price = first_open_price;
            this.pre_open_price = pre_open_price;
            this.open_price = open_price;
            this.next_add_r = next_add_r;
        }

        public String toString() {
            return String.format("%s_%d_%s_%s_%s_%s_%s", super.toString(), in_method, boll, first_open_price, pre_open_price, open_price, next_add_r);
        }

        @Override
        public int getParamNum() {
            return super.getParamNum() + 6;
        }
    }

    private static List<OrderEx> getSameOrders(List<IOrder> orderList, Instrument instrument, String name, int configIndex,
                                               TradeDirection tradeDirection, LabelType labelType, Class<? extends BaseOrderComment> c) {
        List<OrderEx> result = new ArrayList<>();
        //先遍历所有的订单，找到与当前订单类似的
        for (IOrder order : orderList) {
            if (order == null || order.getInstrument() != instrument || order.getState() != IOrder.State.FILLED) {
                continue;
            }
            BaseOrderComment orderComment;
            if (YashOrderComment.class.equals(c)) {
                orderComment = new YashOrderComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(name, configIndex) || tradeDirection != orderEx.tradeDirection) {
                continue;
            }
            if (labelType != null && labelType != orderEx.labelType) {
                continue;
            }
            result.add(orderEx);
        }
        result.sort(Comparator.comparingInt(x -> x.seriesIndex));
        return result;
    }

    private static OrderExManager getOrderExManager(BaseConfig baseConfig, List<IOrder> orderList, Class<? extends BaseOrderComment> c) {

        final OrderExManager o = new OrderExManager();
        for (IOrder order : orderList) {
            BaseOrderComment orderComment;
            if (YashOrderComment.class.equals(c)) {
                orderComment = new YashOrderComment();
            } else {
                continue;
            }
            OrderEx orderEx = new OrderEx(order, orderComment);

            if (!orderEx.valid(baseConfig.name, baseConfig.index) || !orderEx.orderComment.valid) {
                continue;
            }
            if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getState() == IOrder.State.FILLED) {
                o.buyList.add(orderEx);
                o.buy_pl_usd += order.getProfitLossInUSD();
                o.buy_pl_pip += order.getProfitLossInPips();
                o.buy_c += order.getCommissionInUSD();
                if (o.buy_first == null || orderEx.seriesIndex < o.buy_first.seriesIndex) {
                    o.buy_first = orderEx;
                }
                if (o.buy_last == null || orderEx.seriesIndex > o.buy_last.seriesIndex) {
                    o.buy_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getState() == IOrder.State.FILLED) {
                o.sellList.add(orderEx);
                o.sell_pl_usd += order.getProfitLossInUSD();
                o.sell_pl_pip += order.getProfitLossInPips();
                o.sell_c += order.getCommissionInUSD();
                if (o.sell_first == null || orderEx.seriesIndex < o.sell_first.seriesIndex) {
                    o.sell_first = orderEx;
                }
                if (o.sell_last == null || orderEx.seriesIndex > o.sell_last.seriesIndex) {
                    o.sell_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT && order.getState() == IOrder.State.OPENED) {
                o.sellLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT && order.getState() == IOrder.State.OPENED) {
                o.buyLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLSTOP && order.getState() == IOrder.State.OPENED) {
                o.sellStopList.add(orderEx);
            }
        }
        return o;
    }

    //记录曾经的趋势
    class ExTrend {
        final Boll boll;
        final TimeBar bar;
        final TradeDirection td;

        public ExTrend(Boll boll, TimeBar bar, TradeDirection td) {
            this.boll = boll;
            this.bar = bar;
            this.td = td;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ExTrend exTrend = (ExTrend) o;
            return Objects.equals(bar, exTrend.bar) &&
                    td == exTrend.td;
        }

        @Override
        public int hashCode() {
            return Objects.hash(bar, td);
        }

        @Override
        public String toString() {
            return boll + "," + bar + ", " + td.name();
        }
    }
}
